package com.tianji.coupon.service.impl;


import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.constant.CouponCacheConstant;
import com.tianji.common.lock.MyLock;
import com.tianji.common.lock.MyLockStrategy;
import com.tianji.common.lock.MyLockType;
import com.tianji.common.utils.DateUtils;
import com.tianji.coupon.enums.ExchangeCodeStatus;
import com.tianji.coupon.enums.ObtainType;
import com.tianji.coupon.enums.UserCouponStatus;
import com.tianji.coupon.mapper.CouponMapper;
import com.tianji.coupon.mapper.UserCouponMapper;
import com.tianji.coupon.pojo.Coupon;
import com.tianji.coupon.pojo.ExchangeCode;
import com.tianji.coupon.pojo.UserCoupon;
import com.tianji.coupon.service.CouponService;
import com.tianji.coupon.service.ExchangeCodeService;
import com.tianji.coupon.service.UserCouponService;
import com.tianji.coupon.vo.CouponVO;
import com.tianji.exception.BizException;
import com.tianji.common.utils.SecurityUtil;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CodeUtil;
import com.tianji.mq.produce.CouponProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.tianji.common.utils.DateUtils.convertTimestampToLocalDateTime;
import static com.tianji.coupon.enums.CouponStatus.ISSUING;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author 陈佳
 * @since 2024-09-11
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements UserCouponService {

    private final CouponService couponService;

    private final CouponMapper couponMapper;

    private final ExchangeCodeService exchangeCodeService;

    private final RedisTemplate redisTemplate;

    private final CouponProducer couponProducer;

    @Override
    public List<CouponVO> queryIssuingCoupons() {
        //查询出手动领取的并且是发放中的优惠券
        List<Coupon> list = couponService.lambdaQuery().eq(Coupon::getStatus, ISSUING.getValue())
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC.getValue())
                .list();
        if(list.isEmpty()){
            return Collections.emptyList();
        }
        //统计当前用户对优惠券已经领取的数量
        List<Long> ids = list.stream().map(Coupon::getId).collect(Collectors.toList());
        List<UserCoupon> userList = lambdaQuery().eq(UserCoupon::getUserId, 1)
                .in(UserCoupon::getCouponId, ids).list();
        Map<Long, Long> map = userList.stream().collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //统计当前用户已经领取了但是还没使用的数量
        Map<Long, Long> unUsedMap = userList.stream().filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED.getValue())
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        ArrayList<CouponVO> result = new ArrayList<>(list.size());
        for(Coupon coupon : list) {
            CouponVO couponVO = BeanUtils.copyBean(coupon, CouponVO.class);
            result.add(couponVO);
            //是否可以领取
            couponVO.setAvailable(
                coupon.getIssueNum() < coupon.getTotalNum()
                    && map.getOrDefault(coupon.getId(), 0L) < coupon.getUserLimit()
            );
            //是否可以使用
            couponVO.setReceived(unUsedMap.getOrDefault(coupon.getId(),0L)>0);
        }
        return result;
    }


    @Override
    public void receiveCoupon(Long couponId) {
        //1.查询优惠券
        //Coupon coupon = couponMapper.selectById(couponId);
        //从缓存中查询优惠券
        Coupon coupon = queryCouponByCache(couponId);
        if(coupon == null){
            throw new BizException("优惠券不存在");
        }
        //2.校验发放时间
        LocalDateTime now = LocalDateTime.now();
        if(now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())){
            throw new BizException("优惠券尚未开始或已发放");
        }
        //3.校验入库
        if(coupon.getIssueNum() >= coupon.getTotalNum()){
            throw new BizException("优惠券库存不足");
        }
        //4.校验每个人的领取数量
        SecurityUtil.XcUser user = SecurityUtil.getUser();
        Long count = lambdaQuery()
                .eq(UserCoupon::getUserId, user.getUserId())
                .eq(UserCoupon::getCouponId, couponId)
                .count();
        if(count != null && count >= coupon.getUserLimit()){
            throw new BizException("超出领取数量");
        }
        //扣减优惠券库存
        //更新优惠券已经发放的数量
        //couponMapper.incrIssueNum(couponId);
        //直接从缓存中扣减
        redisTemplate.opsForHash().increment(
              CouponCacheConstant.COUPON_CACHE_KEY_PREFIX+couponId,"totalNum",-1
        );

        //更新优惠券已经发放的数量
//        int i = couponMapper.incrIssueNum(couponId,coupon.getIssueNum(),coupon.getUserLimit());
//        if(i == 0){
//            throw new BizException("优惠券库存不足");
//        }

        //新增一个用户券

        //saveUser(coupon,user.getUserId());
        //使用mq进行异步新增
        try {
            couponProducer.sendCouponUpdateMessage(user.getUserId(), couponId);
        } catch (Exception e) {
            //需要恢复到原来的库存
            redisTemplate.opsForHash().increment(
                    CouponCacheConstant.COUPON_CACHE_KEY_PREFIX+couponId,"totalNum",1
            );
            throw new RuntimeException(e);
        }
    }

    private Coupon queryCouponByCache(Long couponId) {
        String key = CouponCacheConstant.COUPON_CACHE_KEY_PREFIX+couponId;
        Map map = redisTemplate.opsForHash().entries(key);
        if(map.isEmpty()){
            return null;
        }
        Coupon coupon = new Coupon();
        coupon.setTotalNum(Integer.parseInt(map.get("totalNum").toString()));
        coupon.setUserLimit(Integer.parseInt(map.get("userLimit").toString()));
        coupon.setIssueBeginTime(convertTimestampToLocalDateTime(map.get("issueBeginTime")));
        coupon.setIssueEndTime(convertTimestampToLocalDateTime(map.get("issueEndTime")));
        coupon.setIssueNum(Integer.parseInt(map.get("issueNum").toString()));
        return coupon;
    }



    @Override
    public void saveUser(Coupon coupon, Long userId) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setCouponId(coupon.getId());
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if(termBeginTime == null){
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(termBeginTime);
        userCoupon.setTermEndTime(termEndTime);
        userCoupon.setUserId(userId);
        save(userCoupon);
    }


    @Override
    public void exchangeCoupon(String code) {
        //1.校验并解析兑换码
        long serialNum = CodeUtil.parseCode(code);
        //2.校验是否已经兑换过 setbit key 4 1,这里直接执行该指令，通过返回值判断是否被兑换过
        boolean exchange = exchangeCodeService.updateExchangeMark(serialNum,true);
        if(exchange){
            throw new BizException("兑换码已经被兑换过");
        }
        try{
            //3.查询兑换码对应的优惠券id
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if(exchangeCode == null){
                throw new BizException("兑换码不存在");
            }
            //4.判断是否过期
            LocalDateTime now = LocalDateTime.now();
            if(now.isAfter(exchangeCode.getExpiredTime())){
                throw new BizException("兑换码已经过期");
            }
            //5.校验并且生成用户券
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            Long userId = SecurityUtil.getUser().getUserId();
            //6.使用锁防止用户券新增线程安全问题
//            synchronized (userId.toString().intern()) {//常量池中获取常量防止锁对象变化
//                UserCouponService userCouponService = (UserCouponService)AopContext.currentProxy();
//                userCouponService.checkAndCreateCoupon(coupon,userId,serialNum);
//            }
            UserCouponService userCouponService = (UserCouponService)AopContext.currentProxy();
            userCouponService.checkAndCreateCoupon(coupon,userId,serialNum);
        }catch (Exception e){
            //重置兑换标志为0未兑换
            exchangeCodeService.updateExchangeMark(serialNum,false);
        }
    }

    //先加锁再加上事务，防止锁提前释放了才提交事务
    //改造成分布式锁
    @MyLock(name = "lock:coupon:#{userId}"
            ,lockType = MyLockType.RE_ENTRANT_LOCK
            ,lockStrategy = MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT)
    @Transactional
    @Override
    public void checkAndCreateCoupon(Coupon coupon, Long userId, long serialNum) {
        //校验每个人的限领数量
        Long count = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId())
                .count();
        if(count != null && count >= coupon.getUserLimit()){
            throw new BizException("超出领取数量");
        }
        //更新优惠券已经发放的数量
        couponMapper.incrIssueNum(coupon.getId(), coupon.getIssueNum(), coupon.getUserLimit());
        //新增一个用户券
        saveUser(coupon,userId);
        //更新兑换码的状态
        if(ObjectUtil.isNotEmpty(serialNum)){
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId,userId)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED.getValue())
                    .eq(ExchangeCode::getId,serialNum)
                    .update();
        }
    }

    @Override
    public Page<Coupon> pageList(int page, int pageSize, int status) {
        List<UserCoupon> list = lambdaQuery()
                .eq(UserCoupon::getUserId, SecurityUtil.getUser().getUserId())
                .eq(UserCoupon::getStatus, status)
                .list();
        Page<Coupon> couponPage = null;
        if(CollectionUtil.isNotEmpty(list)){
            List<Long> collect = list.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
            couponPage = couponService.lambdaQuery()
                    .in(Coupon::getId, collect)
                    .page(new Page<>(page, pageSize));
        }
        return couponPage;
    }
}
